import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.util.concurrent.Executor;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import jdk.internal.vm.Continuation;
import jdk.internal.vm.ContinuationScope;

// java --add-opens java.base/jdk.internal.vm=ALL-UNNAMED Prime5
public final class Prime5 { // buggy because ForkJoinPool & Continuation
	private static final ContinuationScope cs = new ContinuationScope("");
	private static final Executor pool = new ForkJoinPool(); // Executors.newWorkStealingPool(); // ForkJoinPool.commonPool();

	public static final class Task extends ForkJoinTask<Void> implements Runnable {
		private final CoTask coTask;

		public Task(CoTask coTask) {
			this.coTask = coTask;
		}

		@Override
		public Void getRawResult() {
			return null;
		}

		@Override
		protected void setRawResult(Void value) {
		}

		@Override
		public void run() {
			exec();
		}

		@Override
		protected boolean exec() {
			try {
				do
					coTask.run();
				while (!(boolean)CoTask.pausingHandle.getAndSet(coTask, false));
			} catch (Throwable e) {
				//noinspection CallToPrintStackTrace
				e.printStackTrace();
			}
			return false;
		}
	}

	public static final class CoTask extends Continuation {
		static final VarHandle pausingHandle;
		private final Task task;
		int chanValue;
		CoTask waitNext;
		boolean pausing;

		static {
			var l = MethodHandles.lookup();
			try {
				pausingHandle = l.findVarHandle(CoTask.class, "pausing", boolean.class);
			} catch (ReflectiveOperationException e) {
				throw new RuntimeException(e);
			}
		}

		public static void go(Runnable r) {
			pool.execute(new CoTask(r).task);
		}

		public static void pause() {
			Continuation.yield(cs);
		}

		public void resume() {
			if (!(boolean)pausingHandle.getAndSet(this, false))
				pool.execute(task);
		}

		private CoTask(Runnable target) {
			super(cs, target);
			task = new Task(this);
		}
	}

	public static final class IntChan {
		private CoTask waitHead;
		private boolean hasValue;

		public void send(int v) {
			CoTask c;
			synchronized (this) {
				if (hasValue) {
					c = (CoTask)Continuation.getCurrentContinuation(cs);
					c.chanValue = v;
					c.waitNext = waitHead;
					c.pausing = true;
					waitHead = c;
					c = null;
				} else {
					c = waitHead;
					if (c == null) {
						c = (CoTask)Continuation.getCurrentContinuation(cs);
						c.chanValue = v;
						c.waitNext = null;
						c.pausing = true;
						waitHead = c;
						hasValue = true;
						c = null;
					} else
						waitHead = c.waitNext;
				}
			}
			if (c != null) {
				c.chanValue = v;
				c.resume();
			} else
				CoTask.pause();
		}

		public int recv() {
			CoTask c;
			boolean r;
			synchronized (this) {
				if (!hasValue) {
					c = (CoTask)Continuation.getCurrentContinuation(cs);
					c.waitNext = waitHead;
					c.pausing = true;
					waitHead = c;
					r = false;
				} else {
					c = waitHead;
					if (c == null) {
						c = (CoTask)Continuation.getCurrentContinuation(cs);
						c.waitNext = null;
						c.pausing = true;
						waitHead = c;
						hasValue = false;
						r = false;
					} else {
						waitHead = c.waitNext;
						r = true;
					}
				}
			}
			if (r) {
				var v = c.chanValue;
				c.resume();
				return v;
			} else {
				CoTask.pause();
				return c.chanValue;
			}
		}
	}

	static void go(Runnable r) {
		CoTask.go(r);
	}

	static void generate(IntChan ch) {
		//noinspection InfiniteLoopStatement
		for (int i = 2; ; i++)
			ch.send(i);
	}

	static void filter(IntChan in, IntChan out, int prime) {
		//noinspection InfiniteLoopStatement
		for (; ; ) {
			var i = in.recv();
			if (i % prime != 0)
				out.send(i);
		}
	}

	public static void main(String[] args) throws InterruptedException {
		var count = args.length > 0 ? Integer.parseInt(args[0]) : 10000;
		go(() -> {
			var ch = new IntChan();
			var ch0 = ch;
			go(() -> generate(ch0));
			for (int i = 0; ; ) {
				var prime = ch.recv();
				if (++i == count) {
					System.out.println(prime);
					System.exit(0);
				}
				var ch1 = ch;
				var ch2 = new IntChan();
				go(() -> filter(ch1, ch2, prime));
				ch = ch2;
			}
		});
		Thread.sleep(1_000_000L);
	}
}
